﻿using System;
using System.Collections.Generic;
using System.IO;
using GameFramework.Extensions;
using GameFramework.GameObjects;
using Microsoft.Xna.Framework;
using NUnit.Framework;

namespace GameFramework.Tests
{
    [TestFixture]
    public class BinarySerializationTests
    {
        [TestCase]
        public void ReadIntTest()
        {
            const int value = 14;

            SerializeDeserialize(
                writer => writer.WriteInt(value),
                reader => Assert.AreEqual(value, reader.ReadInt32()));
        }

        [TestCase]
        public void ReadFloatTest()
        {
            const float value = 14.6f;

            SerializeDeserialize(
                writer => writer.WriteFloat(value),
                reader => Assert.AreEqual(value, reader.ReadFloat()));
        }

        [TestCase]
        public void ReadDateTimeTest()
        {
            DateTime dateTime = new DateTime(2011, 3, 16);

            SerializeDeserialize(
                writer => writer.WriteDateTime(dateTime),
                reader => Assert.AreEqual(dateTime, reader.ReadDateTime()));
        }

        [TestCase]
        public void ReadTimeSpanTest()
        {
            TimeSpan timeSpan = new TimeSpan(1231287468127);

            SerializeDeserialize(
                writer => writer.WriteTimeSpan(timeSpan),
                reader => Assert.AreEqual(timeSpan, reader.ReadTimeSpan()));
        }

        [TestCase]
        public void ReadRectangleTest()
        {
            Rectangle value = new Rectangle(10, 20, 30, 40);

            SerializeDeserialize(
                writer => writer.WriteRectangle(value),
                reader => Assert.AreEqual(value, reader.ReadRectangle()));
        }

        [TestCase]
        public void ReadColorTest()
        {
            Color value = new Color(1, 2, 3, 4);

            SerializeDeserialize(
                writer => writer.WriteColor(value),
                reader => Assert.AreEqual(value, reader.ReadColor()));
        }

        [TestCase]
        public void ReadVector2Test()
        {
            Vector2 value = new Vector2(1, 2);

            SerializeDeserialize(
                writer => writer.WriteVector2(value),
                reader => Assert.AreEqual(value, reader.ReadVector2()));
        }

        [TestCase]
        public void ReadVector3Test()
        {
            Vector3 value = new Vector3(1, 2, 3);

            SerializeDeserialize(
                writer => writer.WriteVector3(value),
                reader => Assert.AreEqual(value, reader.ReadVector3()));
        }

        [TestCase]
        public void ReadVector4Test()
        {
            Vector4 value = new Vector4(1, 2, 3, 4);

            SerializeDeserialize(
                writer => writer.WriteVector4(value),
                reader => Assert.AreEqual(value, reader.ReadVector4()));
        }

        [TestCase]
        public void ReadStringTests()
        {
            const string value = "Test";

            SerializeDeserialize(
                writer => writer.WriteString(value),
                reader => Assert.AreEqual(value, reader.ReadString()));
        }

        [TestCase]
        public void ReadNullStringTests()
        {
            const string value = null;

            SerializeDeserialize(
                writer => writer.WriteString(value),
                reader => Assert.AreEqual(string.Empty, reader.ReadString()));
        }

        [TestCase]
        public void ReadObjectTest()
        {
            TestObject value = new TestObject("Test", 56.4f);

            SerializeDeserialize(
                writer => writer.WriteObject(value),
                reader => Assert.AreEqual(value, reader.ReadObject<TestObject>()));
        }

        [TestCase]
        public void ReadNullObjectTest()
        {
            TestObject value = null;

            SerializeDeserialize(
                writer => writer.WriteObject(value),
                reader => Assert.AreEqual(null, reader.ReadObject<TestObject>()));
        }

        [TestCase]
        public void ReadListTest()
        {
            var list = new List<TestObject> 
                       {
                           new TestObject("Test1", 1.1f),
                           new TestObject("Test2", 2.2f)
                       };

            SerializeDeserialize(
                writer => writer.WriteList(list),
                reader => CollectionAssert.AreEqual(list, reader.ReadList<TestObject>()));
        }

        [TestCase]
        public void ReadNullListTest()
        {
            List<TestObject> list = null;

            SerializeDeserialize(
                writer => writer.WriteList(list),
                reader => CollectionAssert.AreEqual(new List<TestObject>(), reader.ReadList<TestObject>()));
        }

        [TestCase]
        public void ReadStringListTest()
        {
            var list = new List<string> 
                       {
                           "Test1",
                           "Test2"
                       };

            SerializeDeserialize(
                writer => writer.WriteStringList(list),
                reader => CollectionAssert.AreEqual(list, reader.ReadStringList()));
        }

        [TestCase]
        public void ReadNullStringListTest()
        {
            List<string> list = null;

            SerializeDeserialize(
                writer => writer.WriteStringList(list),
                reader => CollectionAssert.AreEqual(new List<string>(), reader.ReadStringList()));
        }

        [TestCase]
        public void ReadArrayTests()
        {
            var array = new TestObject[]
                       {
                           new TestObject("Test1", 1.1f),
                           new TestObject("Test2", 2.2f)
                       };

            SerializeDeserialize(
                writer => writer.WriteArray(array),
                reader => CollectionAssert.AreEqual(array, reader.ReadArray<TestObject>()));
        }

        [TestCase]
        public void ReadNullArrayTests()
        {
            TestObject[] array = null;

            SerializeDeserialize(
                writer => writer.WriteArray(array),
                reader => CollectionAssert.AreEqual(new TestObject[0], reader.ReadArray<TestObject>()));
        }

        [TestCase]
        public void ReadFloatArrayTests()
        {
            var array = new float[] { 1.1f, 2.2f, 3.3f };

            SerializeDeserialize(
                writer => writer.WriteFloatArray(array),
                reader => CollectionAssert.AreEqual(array, reader.ReadFloatArray()));
        }

        [TestCase]
        public void ReadNullFloatArrayTests()
        {
            float[] array = null;

            SerializeDeserialize(
                writer => writer.WriteFloatArray(array),
                reader => CollectionAssert.AreEqual(new float[0], reader.ReadFloatArray()));
        }

        [TestCase]
        public void ReadVector2ArrayTests()
        {
            var list = new Vector2[]
                       {
                            new Vector2(1.1f, 1.2f),
                            new Vector2(2.1f, 2.2f)
                       };

            SerializeDeserialize(
                writer => writer.WriteVector2Array(list),
                reader => CollectionAssert.AreEqual(list, reader.ReadVector2Array()));
        }

        [TestCase]
        public void ReadNullVector2ArrayTests()
        {
            Vector2[] list = null;

            SerializeDeserialize(
                writer => writer.WriteVector2Array(list),
                reader => CollectionAssert.AreEqual(new Vector2[0], reader.ReadVector2Array()));
        }

        [TestCase]
        public void ReadSpriteObjectTest()
        {
            var value = new Texture();

            SerializeDeserialize(
                writer => writer.WriteObject(value),
                reader => AssertSpriteObjectEqual(value, reader.ReadObject<Texture>()));
        }

        [TestCase]
        public void ReadTextObjectTest()
        {
            var value = new Label();

            SerializeDeserialize(
                writer => writer.WriteObject(value),
                reader => AssertTextObjectEqual(value, reader.ReadObject<Label>()));
        }

        [TestCase]
        public void ReadScreenTest()
        {
            var value = new TestScreen(new Label { Text = "Test1" }, new Texture { Angle = 1f });

            SerializeDeserialize(
                writer => writer.WriteObject(value),
                reader => Assert.AreEqual(value, reader.ReadObject<TestScreen>()));
        }

        #region Test Helpers

        private static void AssertTextObjectEqual(Label expectedTextObject, Label textObject)
        {
            AssertSpriteObjectEqual(expectedTextObject, textObject);

            Assert.AreEqual(expectedTextObject.Text, textObject.Text);
            Assert.AreEqual(expectedTextObject.TextureName, textObject.TextureName);
        }

        private static void AssertSpriteObjectEqual(Texture expectedTexture, Texture texture)
        {
            Assert.AreEqual(expectedTexture.Position, texture.Position);
            Assert.AreEqual(expectedTexture.Origin, texture.Origin);
            Assert.AreEqual(expectedTexture.Scale, texture.Scale);
            Assert.AreEqual(expectedTexture.Angle, texture.Angle);
            Assert.AreEqual(expectedTexture.SourceRectangle, texture.SourceRectangle);
            Assert.AreEqual(expectedTexture.Color, texture.Color);
            Assert.AreEqual(expectedTexture.LayerDepth, texture.LayerDepth);
            Assert.AreEqual(expectedTexture.TextureName, texture.TextureName);
        }

        private static bool TextObjectEqual(Label expectedTextObject, Label textObject)
        {
            return SpriteObjectEqual(expectedTextObject, textObject) &&
                   expectedTextObject.Text == textObject.Text &&
                   expectedTextObject.TextureName == textObject.TextureName;
        }

        private static bool SpriteObjectEqual(Texture expectedTexture, Texture texture)
        {
            return expectedTexture.Position == texture.Position &&
                   expectedTexture.Origin == texture.Origin &&
                   expectedTexture.Scale == texture.Scale &&
                   expectedTexture.Angle == texture.Angle &&
                   expectedTexture.SourceRectangle == texture.SourceRectangle &&
                   expectedTexture.Color == texture.Color &&
                   expectedTexture.LayerDepth == texture.LayerDepth &&
                   expectedTexture.TextureName == texture.TextureName;
        }

        private static void SerializeDeserialize(Action<BinaryWriter> writeFunc, Action<BinaryReader> readFunc)
        {
            using (var memoryStream = new MemoryStream())
            {
                using (var writer = new BinaryWriter(memoryStream))
                {
                    writeFunc(writer);

                    memoryStream.Position = 0;

                    using (var reader = new BinaryReader(memoryStream))
                    {
                        readFunc(reader);
                    }
                }
            }
        }

        public class TestObject : IBinarySerializable
        {
            private string s;
            private float f;

            public TestObject()
            {
            }

            public TestObject(string s, float f)
            {
                this.s = s;
                this.f = f;
            }

            public void Write(BinaryWriter writer)
            {
                writer.WriteString(s);
                writer.WriteFloat(f);
            }

            public void Read(BinaryReader reader)
            {
                s = reader.ReadString();
                f = reader.ReadFloat();
            }

#pragma warning disable 659
            
            public override bool Equals(object obj)
            {
                var other = (TestObject)obj;
                return s == other.s && f == other.f;
            }

#pragma warning restore 659
        }

        public class TestScreen : Screen
        {
            private Label textObject;
            private Texture texture;

            public TestScreen()
            {
            }

            public TestScreen(Label textObject, Texture texture)
            {
                this.textObject = textObject;
                this.texture = texture;
            }

            public override void Write(BinaryWriter writer)
            {
                base.Write(writer);

                writer.WriteObject(textObject);
                writer.WriteObject(texture);
            }

            public override void Read(BinaryReader reader)
            {
                base.Read(reader);

                textObject = reader.ReadObject<Label>();
                texture = reader.ReadObject<Texture>();
            }

            public override bool Equals(object obj)
            {
                var other = (TestScreen)obj;
                return TextObjectEqual(textObject, other.textObject) && SpriteObjectEqual(texture, other.texture);
            }
        }

        #endregion
    }
}
